﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using NLog;

namespace DeSleeper.Library
{
	public class WakeOnLanNetworkInterface
	{
		private const string wakeOnMagicPacketOnlySettingName = "MSNdis_DeviceWakeOnMagicPacketOnly";
		private const string wakeEnableSettingName = "MSPower_DeviceWakeEnable";
		private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

		public NetworkInterface BaseInterface { get; private set; }

		public string DeviceID { get; private set; }
		public WakeOnLanNetworkInterface(string deviceID, string guid)
		{
			DeviceID = deviceID;
			var adapters = from adapter in NetworkInterface.GetAllNetworkInterfaces()
			               where string.Equals(adapter.Id, guid, StringComparison.OrdinalIgnoreCase)
			               select adapter;
			BaseInterface = adapters.SingleOrDefault();
		}

		public WakeOnLanNetworkInterface(string deviceID, NetworkInterface baseInterface)
		{
			DeviceID = deviceID;
			BaseInterface = baseInterface;
		}

		public static IEnumerable<WakeOnLanNetworkInterface> GetAllRelevantInterfaces()
		{
			var adapters = NetworkInterfaceHelper.TryGetAdapters();
			if (adapters == null)
				yield break;

			var baseInterfaces = NetworkInterfaceHelper.TryGetNetworkInterfaces();
			if (baseInterfaces == null)
				yield break;

			foreach (var adapter in adapters)
			{
				NetworkInterface baseAdapter;
				try
				{
					baseAdapter = baseInterfaces.Single(baseInterface =>
					                                    NetworkInterfaceHelper.AreInterfacesEqual(baseInterface, adapter));
				}
				catch(Exception e)
				{
					_logger.TraceException(
						string.Format("Failed to get network interface information for adapter: [{0}].", 
						NetworkInterfaceHelper.TryGetString(adapter, "Name")),
						e);
					continue;
				}

				yield return
					new WakeOnLanNetworkInterface(NetworkInterfaceHelper.TryGetString(adapter, "PNPDeviceID"), baseAdapter);
			}
		}

		public void EnableWakeOnLan()
		{
			setWakeOnLan(true);
		}

		public void DisableWakeOnLan()
		{
			setWakeOnLan(false);
		}

		private void setWakeOnLan(bool enable)
		{
			using (var wakeEnableSetting = getSetting(wakeEnableSettingName))
			{
				if (wakeEnableSetting != null)
				{
					wakeEnableSetting.SetPropertyValue("Enable", enable);
					wakeEnableSetting.Put();
				}
			}

			using (var magicPacketSetting = getSetting(wakeOnMagicPacketOnlySettingName))
			{
				if (magicPacketSetting != null)
				{
					magicPacketSetting.SetPropertyValue("EnableWakeOnMagicPacketOnly", enable);
					magicPacketSetting.Put();
				}
			}
		}

		private readonly ReaderWriterLock _initConfigLock = new ReaderWriterLock();
		private WakeOnLanConfiguration _wakeOnLanConfiguration;

		public WakeOnLanConfiguration WakeOnLanConfiguration
		{
			get
			{
				_initConfigLock.AcquireReaderLock(Timeout.Infinite);

				try
				{
					if (_wakeOnLanConfiguration == null)
					{
						var cookie = _initConfigLock.UpgradeToWriterLock(Timeout.Infinite);
						try
						{
							if (_wakeOnLanConfiguration == null)
							{
								_wakeOnLanConfiguration =
									new WakeOnLanConfiguration
										{
											WakeOnLanEnabled = getSettingState(wakeEnableSettingName, "Enable"),
											MagicPacketEnabled = getSettingState(wakeOnMagicPacketOnlySettingName, "EnableWakeOnMagicPacketOnly")
										};
							}
						}
						finally
						{
							_initConfigLock.DowngradeFromWriterLock(ref cookie);
						}
					}
				}
				finally
				{
					_initConfigLock.ReleaseReaderLock();
				}
				return _wakeOnLanConfiguration;
			}
		}

		private string _macAddress;
		public string MacAddress
		{
			get
			{
				if (_macAddress == null)
				{
					var macString = BaseInterface.GetPhysicalAddress().ToString();
					if (macString.Length == 12)
					{
						var macOut = new StringBuilder();
						for (int i = 0; i < 6; i++)
						{
							macOut.Append(macString[i * 2]);
							macOut.Append(macString[(i * 2) + 1]);
							if (i != 5)
								macOut.Append("-");
						}
						_macAddress = macOut.ToString();
					}
					else _macAddress = macString;
				}
				return _macAddress;
			}
		}

		private ConfigurationState getSettingState(string settingName, string propertyName)
		{
			using (var setting = getSetting(settingName))
			{
				if (setting == null)
					return ConfigurationState.Unsupported;

				var enabled = setting[propertyName] as bool?;
				if (!enabled.HasValue)
					return ConfigurationState.Unsupported;

				return enabled.Value ? ConfigurationState.Enabled : ConfigurationState.Disabled;
			}
		}

		private ManagementObject getSetting(string settingName)
		{
			using (
				var searcher = new ManagementObjectSearcher(new ManagementScope("root/wmi"),
				                                            new ObjectQuery(string.Format("SELECT * FROM {0}", settingName))))
			using (var settings = searcher.Get())
			{
				try
				{
					foreach (var setting in settings)
					{
						var instanceName = NetworkInterfaceHelper.TryGetString(setting, "InstanceName");
						if (instanceName != null && instanceName.StartsWith(DeviceID, StringComparison.InvariantCultureIgnoreCase))
							return setting as ManagementObject;

						setting.Dispose();
					}
				}
				catch (ManagementException e)
				{
					_logger.TraceException(string.Format(CultureInfo.CurrentUICulture, 
						"Management setting {0} was not supported", settingName), 
						e);
					return null;
				}
			}
			return null;
		}
	}
}
